/*
 * Toedo
 * Copyright (C) 2009-2010 J. Devauchelle and contributors.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 3 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

package org.toedo.controllers;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.toedo.controllers.events.EventConstants;
import org.toedo.controllers.events.EventController;
import org.toedo.gui.translations.Messages;
import org.toedo.logging.Log;
import org.toedo.model.Attachment;
import org.toedo.model.BaseNote;
import org.toedo.model.Note;
import org.toedo.model.Project;
import org.toedo.model.Tag;
import org.toedo.model.Task;
import org.toedo.preferences.PreferenceConstants;
import org.toedo.utils.ApplicationUtils;
import org.toedo.utils.SystemUtils;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.XStreamException;
import com.thoughtworks.xstream.io.xml.DomDriver;

public class DataController {
	
	/**
	 * Holder for singleton implementation.
	 */
	private static class DataControllerHolder {
		private static final DataController INSTANCE = new DataController();
	}
	
	/**
	 * Get the unique instance of the DataController.
	 * @return The instance of the DataController
	 */
	public static DataController getInstance() {
		return DataControllerHolder.INSTANCE;
	}
	
	private static final String XML_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; //$NON-NLS-1$

	private static final int RUFL_MAX_SIZE = 5;
	
	private String mFileName;
	private Project mProject;
	
	private List<String> mRecentlyUsedFilesList;
	
	private DataController() {
		mFileName = null;
		mProject = new Project();
		
		loadRecentProjectsList();
		
		Log.getInstance(DataController.class).info("DataController initialized."); //$NON-NLS-1$
	}
	
	public Project getProject() {
		return mProject;
	}
	
	public boolean hasFileName() {
		return (mFileName != null) && (!mFileName.isEmpty());
	}
	
	public String getFileName() {
		return mFileName;
	}
	
	public void setFileName(String value) {
		mFileName = value;
		
		putFilenameInRecentProjectList(mFileName);
	}
	
	public List<String> getRecentlyUsedFilesList() {
		return mRecentlyUsedFilesList;
	}
	
	public void open(String fileName, boolean notify) {
		mFileName = fileName;
		load(notify);
		
		putFilenameInRecentProjectList(mFileName);
	}
	
	public void createNewProject(boolean notify) {
		mFileName = null;
		mProject = new Project();
		
		Controller.getInstance().getPreferenceStore().setValue(PreferenceConstants.LAST_OPEN_PROJECT, ""); //$NON-NLS-1$
		
		if (notify) {
			EventController.getInstance().fireGeneralEvent(EventConstants.EVT_OPEN, null, null);
			Controller.getInstance().setStatusLineMessage("New file created.");
		}
		
		Log.getInstance(DataController.class).debug("New file created."); //$NON-NLS-1$	
	}
	
	private void setupXStream(XStream xstream) {
		
		xstream.processAnnotations(Project.class);
		xstream.processAnnotations(Task.class);
		xstream.processAnnotations(BaseNote.class);
		
		// General Aliases
		xstream.alias("project", Project.class); //$NON-NLS-1$
		xstream.alias("task", Task.class); //$NON-NLS-1$
		xstream.alias("note", Note.class); //$NON-NLS-1$
        xstream.alias("tag", Tag.class); //$NON-NLS-1$
        xstream.alias("attachment", Attachment.class); //$NON-NLS-1$
		
        // Aliases for Project
        xstream.aliasField("Name", Project.class, "mName"); //$NON-NLS-1$ //$NON-NLS-2$
        xstream.aliasField("StartDateDelay", Project.class, "mStartDateDelay"); //$NON-NLS-1$ //$NON-NLS-2$
        xstream.aliasField("DefaultDuration", Project.class, "mDefaultDuration"); //$NON-NLS-1$ //$NON-NLS-2$
        xstream.aliasField("UrgentDelay", Project.class, "mUrgentDelay"); //$NON-NLS-1$ //$NON-NLS-2$
        xstream.aliasField("DefaultPriority", Project.class, "mDefaultPriority"); //$NON-NLS-1$ //$NON-NLS-2$
        xstream.aliasField("MarkupType", Project.class, "mMarkupType"); //$NON-NLS-1$ //$NON-NLS-2$
        xstream.aliasField("DisplayCss", Project.class, "mDisplayCss"); //$NON-NLS-1$ //$NON-NLS-2$
        
        // Aliases for BaseNote
        xstream.aliasField("Id", BaseNote.class, "mId"); //$NON-NLS-1$ //$NON-NLS-2$
        xstream.aliasField("Title", BaseNote.class, "mTitle"); //$NON-NLS-1$ //$NON-NLS-2$
        xstream.aliasField("Content", BaseNote.class, "mContent"); //$NON-NLS-1$ //$NON-NLS-2$
        xstream.aliasField("CreationDate", BaseNote.class, "mCreationDate"); //$NON-NLS-1$ //$NON-NLS-2$
        xstream.aliasField("ModificationDate", BaseNote.class, "mModificationDate"); //$NON-NLS-1$ //$NON-NLS-2$
        xstream.aliasField("IsViewOpen", BaseNote.class, "mIsViewOpen"); //$NON-NLS-1$ //$NON-NLS-2$
        
		// Aliases for Tasks
        xstream.aliasField("Priority", Task.class, "mPriority"); //$NON-NLS-1$ //$NON-NLS-2$
        xstream.aliasField("StartDate", Task.class, "mStartDate"); //$NON-NLS-1$ //$NON-NLS-2$
        xstream.aliasField("DueDate", Task.class, "mDueDate"); //$NON-NLS-1$ //$NON-NLS-2$
        xstream.aliasField("Status", Task.class, "mStatus"); //$NON-NLS-1$ //$NON-NLS-2$
        
        // Aliases for Tags
        xstream.aliasField("Id", Tag.class, "mId"); //$NON-NLS-1$ //$NON-NLS-2$
        xstream.aliasField("Name", Tag.class, "mName"); //$NON-NLS-1$ //$NON-NLS-2$
        
        // Aliases for Attachments
        xstream.aliasField("Id", Attachment.class, "mId"); //$NON-NLS-1$ //$NON-NLS-2$
        xstream.aliasField("Type", Attachment.class, "mType"); //$NON-NLS-1$ //$NON-NLS-2$
        xstream.aliasField("Title", Attachment.class, "mTitle"); //$NON-NLS-1$ //$NON-NLS-2$
        xstream.aliasField("Link", Attachment.class, "mLink"); //$NON-NLS-1$ //$NON-NLS-2$
	}
	
	public void save() {
		try {
			XStream xstream = new XStream(new DomDriver());							
			
			setupXStream(xstream);
			
			OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(mFileName), "UTF-8"); //$NON-NLS-1$
			osw.write(XML_HEADER);
			
			try {				
				xstream.toXML(mProject, osw);
			} finally {			
				osw.flush();
				osw.close();
			}

		} catch (FileNotFoundException e) {
			Log.getInstance(DataController.class).warn(e.getMessage());
			e.printStackTrace();
		} catch (IOException ioe) {
			Log.getInstance(DataController.class).warn(ioe.getMessage());
			ioe.printStackTrace();
		}
		mProject.setModified(false);
		
		Controller.getInstance().getPreferenceStore().setValue(PreferenceConstants.LAST_OPEN_PROJECT, mFileName);
		
		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_SAVE, null, null);
		
		Log.getInstance(DataController.class).debug(String.format("End of writing: %s", mFileName)); //$NON-NLS-1$
		
		Controller.getInstance().setStatusLineMessage(String.format(Messages.DataController_StatusLine_Saved, mFileName));
	}
	
	private void load(boolean notify) {
		
		String errorMessage = null;
		
		try {
            XStream xstream = new XStream(new DomDriver());
            
            setupXStream(xstream);
            
            FileInputStream fis = new FileInputStream(new File(mFileName));
            
            try {
            	mProject = (Project) xstream.fromXML(fis);                                
            } finally {
                fis.close();
            }
            
        } catch (FileNotFoundException e) {
        	errorMessage = String.format("Unable to open %s (%s): file does not exists.", mFileName, e.getMessage());        	 //$NON-NLS-1$
        } catch (IOException ioe) {
        	errorMessage = String.format("Unable to open %s (%s): IO Exception.", mFileName, ioe.getMessage()); //$NON-NLS-1$
        } catch (XStreamException e) {
        	errorMessage = String.format("Unable to open %s (%s): invalid file format.", mFileName, e.getMessage()); //$NON-NLS-1$
        }
        
        if (errorMessage == null) {
        	Controller.getInstance().getPreferenceStore().setValue(PreferenceConstants.LAST_OPEN_PROJECT, mFileName);

        	if (notify) {
        		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_OPEN, null, null);
        		Controller.getInstance().setStatusLineMessage(String.format(Messages.DataController_StatusLine_Opened, mFileName));
        	}

        	Log.getInstance(DataController.class).debug(String.format("End of reading: %s", mFileName)); //$NON-NLS-1$
        	
        } else {
        	Log.getInstance(DataController.class).warn(errorMessage);        	
        	ApplicationUtils.notifyUser("Unable to open file", errorMessage); //$NON-NLS-1$
        	
        	createNewProject(notify);
        }
	}

	private void putFilenameInRecentProjectList(String fileName) {
		int currentIndex = mRecentlyUsedFilesList.indexOf(fileName); 
		if (currentIndex == -1) {			
			if (mRecentlyUsedFilesList.size() == RUFL_MAX_SIZE) {
				mRecentlyUsedFilesList.remove(RUFL_MAX_SIZE - 1);
			}			
		} else {
			mRecentlyUsedFilesList.remove(currentIndex);
		}
		mRecentlyUsedFilesList.add(0, fileName);
		
		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_MODIFY_RUF, null, null);
	}
	
	public void loadRecentProjectsList() {
		mRecentlyUsedFilesList = new ArrayList<String>();
		
		File fileList = new File(SystemUtils.recentlyUsedFilesListFile);
		
		if ((fileList.exists()) &&
				(fileList.isFile())&& 
				(fileList.canRead())) {
			
			try {
				
				FileReader reader = new FileReader(fileList);
				BufferedReader br = new BufferedReader(reader); 
				
				String line;
				while((line = br.readLine()) != null) { 
					if (!line.isEmpty()) {
						mRecentlyUsedFilesList.add(line);
					}
				}
				
			} catch (FileNotFoundException e) {
				Log.getInstance(DataController.class).warn("Unable to load recently used file: " + e.getMessage());
			} catch (IOException e) {
				Log.getInstance(DataController.class).warn("Unable to load recently used file: " + e.getMessage());
			}
		}
	}
	
	public void saveRecentProjectsList() {
		
		Log.getInstance(DataController.class).debug("Saving recently used projects list.");
		
		File fileList = new File(SystemUtils.recentlyUsedFilesListFile);
		
		if (fileList.exists()) {
			fileList.delete();
		}
		
		try {
			fileList.createNewFile();
			
			FileWriter writer = new FileWriter(fileList);
			
			Iterator<String> iter = mRecentlyUsedFilesList.iterator();
			while (iter.hasNext()) {
				writer.append(iter.next() + '\n');
			}
			
			writer.flush();
			writer.close();
			
		} catch (IOException e) {
			Log.getInstance(DataController.class).warn("Unable to save recently projects list: " + e.getMessage());
		}
		
	}
	
	public void clearRecentProjectsPList() {
		mRecentlyUsedFilesList.clear();
		EventController.getInstance().fireGeneralEvent(EventConstants.EVT_MODIFY_RUF, null, null);
	}
}
